package com.derekandbritt.booST.endpoints;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.derekandbritt.booST.data.Challenge;
import com.derekandbritt.booST.data.User;
import com.derekandbritt.booST.exceptions.ConnectionException;
import com.derekandbritt.booST.exceptions.DataFormatException;
import com.derekandbritt.booST.exceptions.GenericException;
import com.derekandbritt.koko.configuration.ApplicationConfig;
import com.derekandbritt.koko.configuration.ConfigurationUtil;
import com.derekandbritt.koko.configuration.DataDefinition;
import com.derekandbritt.koko.configuration.DataType;
import com.derekandbritt.koko.emotion.EmotionType;
import com.derekandbritt.koko.events.ApplicationEvent;
import com.derekandbritt.koko.events.DataInstance;
import com.derekandbritt.koko.gson.CustomGson;
import com.google.gson.Gson;

public class KokoEndpoint extends JSONEndpoint {

	private static final String ENDPOINT_NAME = "koko";
	private static final String DEFAULT_HOST_NAME = "bombadil.csc.ncsu.edu:8080";  // 10.0.2.2:8080 for localhost
	private static final String DEFAULT_CONTEXT_ROOT = "Koko_Endpoints";
	private static final String APP_ID = "booST-v01";
	
	private static final String LOG_TAG = "KokoEndpoint";
	private static final Gson gson = CustomGson.getCustomGson();
	
	private static HandlerThread kokoThread = null;
	private static KokoHandler handler = null;
    
    private static HashSet<EmotionType> emotionTypes = new HashSet<EmotionType>();
    private static ArrayList<DataDefinition> dataDefinitions = new ArrayList<DataDefinition>();
    static {
        emotionTypes.add(EmotionType.PRIDE);
        emotionTypes.add(EmotionType.SHAME);
        
        // create a comma separated list of challenge statuses
        StringBuffer messageStatus = new StringBuffer();
        for(Challenge.Status status : Challenge.Status.values()) {
            messageStatus.append(status.toString() + ",");
        }
        messageStatus.deleteCharAt(messageStatus.length() - 1);
        
        //TODO define additional dataDefinitions...
        dataDefinitions.add(ConfigurationUtil.createEnumDataDefinition("isSender", "true,false"));
        dataDefinitions.add(ConfigurationUtil.createEnumDataDefinition("msgStatus", messageStatus.toString()));
        dataDefinitions.add(new DataDefinition("selfEnergy", DataType.DOUBLE));
        dataDefinitions.add(new DataDefinition("otherEnergy", DataType.DOUBLE));
        dataDefinitions.add(new DataDefinition("selfEmotion", DataType.DOUBLE));
        dataDefinitions.add(new DataDefinition("otherEmotion", DataType.DOUBLE));
        dataDefinitions.add(new DataDefinition("selfWin10", DataType.INT));
        dataDefinitions.add(new DataDefinition("selfComplete10", DataType.INT));
        dataDefinitions.add(new DataDefinition("selfActive1", DataType.INT));
    }
    
    private OpenSocialEndpoint osEndpoint;
    private booSTEndpoint booSTEndpoint;
	private boolean useBackgroundThread = true;
		
	public KokoEndpoint(OpenSocialEndpoint osEndpoint, booSTEndpoint booSTEndpoint) {
	    
	    this.osEndpoint = osEndpoint;
	    this.booSTEndpoint = booSTEndpoint;
	    
	    // init the koko thread
        if(handler == null) {
            if(kokoThread == null) {
                kokoThread = new HandlerThread("KokoThread");
                kokoThread.start();
            }
            handler = new KokoHandler(kokoThread.getLooper());
        }
	}
	
   @Override
    protected Gson getGson() {
        return gson;
    }
	
	public void setUseBackgroundThread(boolean usingThread) {
	    this.useBackgroundThread = usingThread;
	}
	
	public String getEndpointName() {
		return ENDPOINT_NAME;
	}
	
	public String getDefaultHostName() {
		return DEFAULT_HOST_NAME;
	}

	public String getDefaultContextRoot() {
		return DEFAULT_CONTEXT_ROOT;
	}

	
	public boolean isAppRegistered() throws ConnectionException, DataFormatException {
        
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("appId", APP_ID);
        
        String jsonResult = getData("JSON/RegisterApplication", map, true);
		
		boolean result = gson.fromJson(jsonResult, boolean.class);
		return result;
	}
	
	public void registerApp() throws ConnectionException, DataFormatException {
		
		ApplicationConfig appConfig = new ApplicationConfig();
		appConfig.setAppID(APP_ID);
		appConfig.setModelId("DecisionTreeModel-v01");
		appConfig.setEmotionTypes(emotionTypes);
		appConfig.setDataDefinitions(dataDefinitions);
		
		String json = gson.toJson(appConfig, ApplicationConfig.class);
		sendData("JSON/RegisterApplication", json);
	}
	
	public boolean isUserRegistered(String userId) throws ConnectionException, DataFormatException {
	
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("userId", userId);
        
        String jsonResult = getData("JSON/RegisterUser", map, true);
		
		boolean result = gson.fromJson(jsonResult, boolean.class);
		return result;
	}
	
	public void registerUser(String userId, String firstName, String lastName) throws ConnectionException, DataFormatException {
			    
		com.derekandbritt.koko.agent.User user = new com.derekandbritt.koko.agent.User();
		user.setUserId(userId);
		user.setFirstName(firstName);
		user.setLastName(lastName);
		
		String json = gson.toJson(user, com.derekandbritt.koko.agent.User.class);
		sendData("JSON/RegisterUser", json);
	}
	
	public boolean registerAppUser(String userId) throws ConnectionException, DataFormatException {
        	    
		HashMap<String, String> map = new HashMap<String, String>();
        map.put("appId", APP_ID);
		map.put("userId", userId);
        
        String jsonResult = getData("JSON/RegisterApplicationUser", map, true);
		
		boolean result = gson.fromJson(jsonResult, boolean.class);
		return result;
	}
	
	public void sendEvent(Challenge challenge, EmotionType classifier) throws ConnectionException, DataFormatException {
	    sendEvent(new CustomChallengeEvent(challenge, classifier));
	}
	
    private void sendEvent(CustomChallengeEvent challengeEvent) throws ConnectionException, DataFormatException {

	    
	    if(useBackgroundThread && !handler.inKokoThread(Thread.currentThread())) {
	        Message msg = handler.obtainMessage(KokoHandler.SEND_CHALLENGE_EVENT, challengeEvent);
	        handler.sendMessage(msg);
	        return;
	    }

	    Challenge challenge = challengeEvent.challenge;
	    
	    String userId = osEndpoint.getPersonId();
	    boolean isSender = userId.equals(challenge.getSender());
	    String opponentId = (isSender) ? challenge.getReceiver() : challenge.getSender();
	    int flags = User.INCLUDE_ENERGY_FLAG | User.INCLUDE_EMOTION_FLAG | 
	                User.INCLUDE_STATISTICS_FLAG | User.IGNORE_CACHE_FLAG;
	    
	    User user = booSTEndpoint.getUser(userId, flags);
	    User opponent = booSTEndpoint.getUser(opponentId, flags);
   
	    ArrayList<DataInstance> instances = new ArrayList<DataInstance>();
	    
	    for(DataDefinition definition : dataDefinitions) {
	        Serializable value = null;
        
	        if(definition.getName().equals("isSender")) {
	            value = (isSender) ? "true" : "false";
            } else if(definition.getName().equals("msgStatus")) {
                value = challenge.getStatus().toString();
            } else if(definition.getName().equals("selfEnergy")) {
                if(user.currentEnergy == null)
                    value = 0.0;
                else
                    value = user.currentEnergy.getLevel(); 
            } else if(definition.getName().equals("otherEnergy")) {
                if(opponent.currentEnergy == null)
                    value = 0.0;
                else
                    value = opponent.currentEnergy.getLevel();
            } else if(definition.getName().equals("selfEmotion")) {
                if(user.currentEmotion == null)
                    value = 0.0;
                else
                    value = user.currentEmotion.getLevel();
            } else if(definition.getName().equals("otherEmotion")) {
                if(opponent.currentEmotion == null)
                    value = 0.0;
                else
                    value = opponent.currentEmotion.getLevel();
            } else if(definition.getName().equals("selfWin10")) {
                // wins in the last 10 attempts
                value = user.statistics.win10;
            } else if(definition.getName().equals("selfComplete10")) {
                // completed challenge in the last 10 days
                value = user.statistics.completed10;
            } else if(definition.getName().equals("selfActive1")) {
                // minutes of activity in the last 24 hours
                value = user.statistics.active24;
            } else {
                throw new DataFormatException("Unknown data definition");
            }
        
	        instances.add(new DataInstance(definition, value));
	    }    
        
	    ApplicationEvent event = new ApplicationEvent(userId, APP_ID, instances);
	    String json = gson.toJson(event, ApplicationEvent.class);
	    
	    if(challengeEvent.emotionType != null) {
	        HashMap<String, String> map = new HashMap<String, String>();
	        map.put("classifier", challengeEvent.emotionType.getId() + "");
	        sendData("JSON/ApplicationEvent", map, json);
	    } else {
	        sendData("JSON/ApplicationEvent", json);
	    }
	}
	
	private class CustomChallengeEvent {
	    
	    public Challenge challenge;
	    public EmotionType emotionType;
	    
	    public CustomChallengeEvent(Challenge challenge, EmotionType emotionType) {
	        this.challenge = challenge;
	        this.emotionType = emotionType;
	    }
	}
	
	private class KokoHandler extends Handler {
	    
	    public static final int SEND_CHALLENGE_EVENT   = 0x01;
	    
	    public KokoHandler(Looper looper) {
	        super(looper);
	    }
	    
	    public boolean inKokoThread(Thread currentThread) {
	        return this.getLooper().getThread().equals(Thread.currentThread());
	    }
	    
	    @Override
	    public void handleMessage(Message msg) {
            switch(msg.what) {
                case SEND_CHALLENGE_EVENT:
                    try { sendEvent((CustomChallengeEvent)msg.obj); }
                    catch(GenericException e) { Log.e(LOG_TAG, "Unable to generate event from challenge", e); }
                    break;
            }
	    }
	}
}
